【解题报告】洛谷P1896 [SOCI2005]互不侵犯
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
注:数据有加强(2018/4/25)
输入格式
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式
所得的方案数
输入输出样例
输入 #1
输出 #1
思路
互不侵犯是一道非常经典的 状态压缩Dp的题目(最主要我最近在练习状态压缩DP)
为什么不能用搜索呢,因为状态数量太多了,是一个指数级别的大爆搜,肯定不能过,而且,这个棋盘的某个格子放不放棋子是两个状态,0不放,1放,因此我们可以使用一个十进制数来表示一个二进制数,而二进制数又是一串数字,正好是由0和1组成的一个串,在看N≤9,因此存的下,所以我们用一个二进制数来表示每一行的状态,然而有一些状态是无用的,我们可以预处理一下(想一想,为什么)
预处理好每一行的状态之后,我们继续处理相邻两行之间的状态,处理方式和处理一行的时候的方式是一样的,我们都需要使用位运算,处理完之后,我们可以写出一个状态转移方程
f[i+1][w+king[now]][stnow]+=f[i][w][stformer](w+king[now]≤k)
其中f[i][j][k]表示到了第i行,第i行及以前已经有j个国王了,当前第i行的状态为k(一个二进制数)
所以我们就可以开始动态规划了,时间复杂度很高,但是已经足以把这道题目过掉了
这道题非常重要,强烈建议读者独自写出代码
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <string> using namespace std; const int maxn=11; int n,k; bool st[1<<maxn]; bool dst[1<<maxn][1<<maxn]; int king[1<<maxn]; long long f[maxn][100][1<<maxn]; int main() { cin>>n>>k; for(int i=0;i<(1<<n);i++) { if(i&(i>>1)) continue; st[i]=true; for(int j=1;j<=i;j<<=1) if(j&i) king[i]++; } for(int i=0;i<(1<<n);i++) { if(st[i]) { for(int j=0;j<(1<<n);j++) { if(st[j]&&!(i&j)&&!(i&(j<<1))&&!(i&(j>>1))) dst[i][j]=true; } } } for(int i=0;i<(1<<n);i++) { if(st[i]) f[1][king[i]][i]=1; } for(int i=1;i<n;i++) { for(int poss=0;poss<(1<<n);poss++) { if(st[poss]) { for(int next=0;next<(1<<n);next++) { if(st[next]&&dst[poss][next]) { for(int w=king[poss];w+king[next]<=k;w++) { f[i+1][w+king[next]][next]+=f[i][w][poss]; } } } } } } long long ans=0; for(int i=0;i<(1<<n);i++) ans+=f[n][k][i]; cout<<ans<<endl; return 0; }
|